package cn.st4rlight.util.regexp;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Maps;

import cn.st4rlight.util.value.DefaultUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * @author st4rlight <st4rlight@163.com>
 * Created on 2024-05-04
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegExpUtil {

    /**
     * 缓存正则表达式
     * NOTE: Pattern对象是线程安全的，所以可以缓存
     */
    private static final Map<String, Pattern> REG_PATTERN_MAP_CACHE = Maps.newConcurrentMap();
    // 身份证正则
    private static final Pattern IDENTITY_NO_PATTERN = Pattern.compile(
        "[1-9]\\d{5}(19|20)\\d{2}"
            + "((0[1-9])|(1[0-2]))((0[1-9])|([1-2]\\d|(3[0-1])))"
            + "\\d{3}[0-9X]"
    );
    // 姓名正则
    private static final Pattern CHINESE_NAME_PATTERN = Pattern.compile(
        "^([\\u4e00-\\u9fa5]{2,8}(·[\\u4e00-\\u9fa5]{2,4})?|[a-zA-Z.\\s]{2,20})$"
    );
    // 邮箱正则
    private static final Pattern EMAIL_PATTERN = Pattern.compile(
        "^\\w+[A-Za-z0-9.\\-]{3,18}@\\w+[A-Za-z0-9.\\-]*\\.\\w+([A-Za-z0-9.\\-])*$"
    );
    // 手机号正则
    private static final Pattern MOBILE_PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
    // 银行卡号正则
    private static final Pattern BANK_CARD_PATTERN = Pattern.compile("^\\d{16,22}$");
    // 地址正则
    private static final Pattern ADDRESS_PATTERN = Pattern.compile(
        "(?<province>[^省]+自治区|.*?省｜.*?行政区|.*市)"
                + "|(?<city>[^市]+自治州|.*?地区|.*?行政单位|.+盟|市辖区|.*?市|.*?县)"
                + "|(?<region>[^县]+县|.*?区｜.+市｜.+旗｜.+海域｜.+岛)?"
    );
    /**
     * 合法的传入变量正则表达式
     * 只允许数字、字母、空格、逗号、星号、减号、下划线、点的组合形式
     */
    private static final Pattern LEGAL_VAR_PATTERN =
            Pattern.compile("^[\\u4e00-\\u9fa5\\uff00-\\uffefa-zA-Z0-9-_. ,*:]+$");


    /**
     * 我国公民的身份证号码特点如下
     * 1.长度18位
     * 2.第1-17号只能为数字
     * 3.第18位只能是数字或者X（电签平台统一大写X）
     * 4.第7-14位表示特有人的年月日信息
     **/
    public static boolean isValidIdentityNo(String identityNo) {
        if (StringUtils.isBlank(identityNo) || identityNo.contains("*")) {
            return false;
        }
        return IDENTITY_NO_PATTERN.matcher(identityNo).matches() && identityNoRuleCheck(identityNo);
    }

    /**
     * 18位身份证规则校验
     * <a href="https://blog.csdn.net/tg928600774/article/details/114303815">参考文档</a>
     */
    private static boolean identityNoRuleCheck(String identityNo) {
        identityNo = DefaultUtil.nullToEmpty(identityNo);
        // 仅适用于18位标准身份证号码
        if (identityNo.length() != 18) {
            return false;
        }

        // 使身份证号码中的字母升大写
        identityNo = identityNo.toUpperCase();
        char[] idCardChars = identityNo.toCharArray();

        // 重点1：加权因子计算
        final int[] factors = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
        int sumFactor = 0;
        for (int i = 0; i < factors.length; i++) {
            // 数字的ASCII码是48-57
            int value = idCardChars[i] - '0';
            // 纯数字校验
            if (value < 0 || value > 9) {
                return false;
            }
            sumFactor += factors[i] * value;
        }

        // 重点2：校验码比对
        final char[] verifyCode = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
        return idCardChars[17] == verifyCode[sumFactor % 11];
    }

    /**
     * 姓名校验
     * <a href="https://blog.csdn.net/sitonn/article/details/114198845">参考文档，做了额外改造支持复姓点形式</a>
     * 1、不能有特殊字符和数字；
     * 2、可以输入英文，可以有空格，可以输入英文名字中的点；
     * 3、可以输入汉字；
     * 4、中文英文不能同时出现；
     * 5、长度在2-20；
     * 6、支持复姓和点形式（比如 爱新觉罗·溥仪）
     */
    public static boolean isValidChineseName(String personName) {
        if (StringUtils.isBlank(personName) || personName.contains("*")) {
            return false;
        }
        return CHINESE_NAME_PATTERN.matcher(personName).matches();
    }

    /**
     * 邮箱校验
     * <a href="https://blog.csdn.net/qq_36270361/article/details/106120936">参考文档</a>
     */
    public static boolean isValidEmail(String email) {
        if (StringUtils.isBlank(email) || email.contains("*")) {
            return false;
        }
        return EMAIL_PATTERN.matcher(email).matches();
    }

    /**
     * 手机号校验
     */
    public static boolean isValidMobilePhone(String mobilePhone) {
        if (StringUtils.isBlank(mobilePhone) || mobilePhone.contains("*")) {
            return false;
        }
        return MOBILE_PHONE_PATTERN.matcher(mobilePhone).matches();
    }

    /**
     * 银行卡号校验
     */
    public static boolean isValidBankCardNo(String bankCardNo) {
        if (StringUtils.isBlank(bankCardNo) || bankCardNo.contains("*")) {
            return false;
        }
        return BANK_CARD_PATTERN.matcher(bankCardNo).matches();
    }

    /**
     * 计算正则表达式（带缓存）
     */
    public static Pattern compileRegExpWithCache(String regExp) {
        return REG_PATTERN_MAP_CACHE.computeIfAbsent(regExp, Pattern::compile);
    }

    /**
     * 获取地址匹配器
     */
    public static Matcher getAddressMatcher(String address) {
        return ADDRESS_PATTERN.matcher(address);
    }

    /**
     * 是否是合法的变量表达式
     */
    public static boolean isLegalVariable(String var) {
        return LEGAL_VAR_PATTERN.matcher(var).matches();
    }
}